call
dry-monads
的特性處理預期及非預期錯誤use case
內利用 steps
拆解每一個步驟,讓程式碼易讀dry-initializer
讓依賴的元件可被替換,進而使程式碼易測試require 'dry/monads/all'
require 'wisper'
require 'dry/initializer'
module Boxenn
class UseCase
extend Dry::Initializer
include Wisper::Publisher
include Dry::Monads
include Dry::Monads::Do
def call(*args)
Success(yield(steps(*args)))
rescue Dry::Monads::Do::Halt
raise
rescue StandardError => e
Failure.new([e], trace: e.backtrace.first)
end
protected
def steps
raise NotImplementedError
end
end
end
這邊用建立訂單當作例子,假設建立訂單需要驗證訂單及發送行銷信給客戶。
class CreateOrder < Boxenn::UseCase
option :repo, default: -> { OrderRepository.new }
option :marketing_client, default: -> { Marketing::Client.new }
def steps(params:)
order = build_order(params)
yield valid_order(order) # 因為回傳是 Dry::Monads::Result 所以需要 yield
create_order(order)
send_marketing_email(order.serial_number)
end
private
def build_order(params)
OrderEntity.new(params)
end
def valid_order(order)
# 假設 order entity 有個 method valid? 會回傳 Bool 告知是否合法
return Success() if order.valid?
Failure('Invalid Order')
end
def create_order(order)
repo.save(order)
end
def send_marketing_email(serial_number)
marketing_client.send_purchase_email(order_serial_number: serial_number)
Success() # 最後一個 call 的 method 需要回傳 Dry::Monads::Result
end
end
use_case = CreateOrder.new
use_case.call(params: params) # => Success()
下一篇將說明 domain 間的依賴關係,以及 pub-sub pattern 的使用。